#include "LibGL/GxTextures.h"
#include "Basic/FileSystem.h"
#include "gl/gl.h"
#include "gl/glu.h"
#include "gl/glext.h"
#include "stdio.h"

namespace GxRendering
{
	const int TextureVersion = 100;

	struct TextureHeader
	{
		char Identifier[3];
		int Version;
		int Width;
		int Height;
		bool UseHalfFloat;
		unsigned char Channels;
		Texture::TextureDataType DataType;
		unsigned char Unused[128];

	};

	TextureLoadFailedException::TextureLoadFailedException(const String & fname)
	{
		FileName = fname;
	}

	Texture::Texture()
	{
		Width = Height =0;
		Filter = Nearest;
		DataType = Char;
		PixelsF = 0;
		Pixels = 0;
		TexID = -1;
		glEnable(GL_TEXTURE_2D);
		glGenTextures(1,&TexID);
		if (TexID != -1)
		{
			glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT);
			glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT);
			glTexEnvi(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE);
		}
	}

	Texture::~Texture()
	{
		glDeleteTextures(1,&TexID);
		ReleaseNativeMemory();
	}

	void Texture::CreateAndUpload()
	{
		if (TexID != -1)
			glDeleteTextures(1, & TexID);
		glGenTextures(1,&TexID);
		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT);
		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT);
		glTexEnvi(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE);
		UploadTexture();
	}

	void Texture::ReleaseNativeMemory()
	{
		if (Pixels)
		{
			delete [] Pixels;
			Pixels = 0;
		}
		if (PixelsF)
		{
			delete [] PixelsF;
			PixelsF = 0;
		}
	}

	void Texture::LoadErrorTexture()
	{
		Width = Height = 2;
		Channels = 3;
		DataType = Char;
		ReleaseNativeMemory();
		Pixels = new unsigned char[Width*Height*Channels];
		Pixels[0] = 255; Pixels[1] = 127; Pixels[2] = 0;
		Pixels[3] = Pixels[4] = Pixels[5] = 0;
		Pixels[6] = 255; Pixels[7] = 127; Pixels[8] = 0;
		Pixels[9] = Pixels[10] = Pixels[11] = 0;
		UploadTexture();
		Name = L"Error";
	}

	void Texture::LoadToMemory(const String & fileName)
	{
		TextureHeader header;
		FILE *f = 0;
		_wfopen_s(&f,fileName.GetStrPtr(),L"rb");
		if (!f)
			throw TextureLoadFailedException(fileName);
		ReleaseNativeMemory();
		try
		{
			fread(&header, sizeof(TextureHeader), 1, f);
			Width = header.Width;
			Height = header.Height;
			Channels = header.Channels;
			DataType = header.DataType;
			UseHalfFloat = header.UseHalfFloat;
			if (TextureVersion != header.Version)
				throw 0;
			bool headerValid = (header.Identifier[0] == 'G' && header.Identifier[1] == 'X' && header.Identifier[2] == 'T');
			if (!headerValid)
				throw 0;
			if (DataType != Float)
			{
				Pixels = new unsigned char[Width*Height*Channels];
				fread(Pixels,1,Width*Height*Channels,f);
			}
			else
			{
				PixelsF = new float[Width*Height*Channels];
				fread(PixelsF, sizeof(float), Width*Height*Channels, f);
			}
			if (f)
				fclose(f);
		}
		catch(int)
		{
			if (f)
				fclose(f);
			throw TextureLoadFailedException(fileName);
		}
	}

	void Texture::LoadFromFile(const String & fileName, bool release, TextureFilter filter)
	{
		LoadToMemory(fileName);

		Filter = filter;
		UploadTexture();
		if (release)
			ReleaseNativeMemory();
		Name = ExtractFileName(fileName);
	}

	void Texture::SaveToFile(const String & fileName)
	{
		TextureHeader header;
		FILE *f = 0;
		_wfopen_s(&f,fileName.GetStrPtr(),L"wb");
		if (!f)
			throw TextureLoadFailedException(fileName);

		memset(&header, 0, sizeof(header));
		header.Width = Width;
		header.Height = Height;
		header.Channels = Channels;
		header.UseHalfFloat = UseHalfFloat;
		header.DataType = DataType;
		header.Version = TextureVersion;
		header.Identifier[0] = 'G';
		header.Identifier[1] = 'X';
		header.Identifier[2] = 'T';
		fwrite(&header, sizeof(TextureHeader), 1, f);
		if (DataType != Float)
		{
			fwrite(Pixels,1,Width*Height*Channels,f);
		}
		else
		{
			fwrite(PixelsF, sizeof(float), Width*Height*Channels, f);
		}
		if (f)
			fclose(f);
	}

	void Texture::UploadTexture()
	{
		glBindTexture(GL_TEXTURE_2D,TexID);

		if (Filter == Linear)
		{
			glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
			glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_LINEAR);
		}
		else if (Filter == Nearest)
		{
			glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);
			glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
		}
		else
		{
			glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
			glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
			glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_LINEAR);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, (int)Filter);
		}
		AverageRate = vec3();
		int samples = Width * Height / (16*16);
		if (samples > 200)
			samples = 200;
		if (samples < 4)
			samples = 4;
		if (DataType != Float)
		{
			for (int i=0; i<samples; i++)
			{
				int x = (int)(Width * rand()/(float)RAND_MAX);
				int y = (int)(Height * rand()/(float)RAND_MAX);
				float r = Pixels[y*Width*Channels + x*Channels]/255.0f;
				float g = Pixels[y*Width*Channels + x*Channels +1]/255.0f;
				float b = Pixels[y*Width*Channels + x*Channels +2]/255.0f;
				AverageRate.x += r;
				AverageRate.y += g;
				AverageRate.z += b;
			}
			AverageRate *= 1.0f/samples;
			glTexImage2D(GL_TEXTURE_2D,0,Channels,Width,Height,0,Channels==3?GL_RGB:GL_RGBA,GL_UNSIGNED_BYTE,Pixels);
		}
		else
		{
			for (int i=0; i<samples; i++)
			{
				int x = (int)(Width * rand()/(float)RAND_MAX);
				int y = (int)(Height * rand()/(float)RAND_MAX);
				float r = PixelsF[y*Width*Channels + x*Channels];
				float g = PixelsF[y*Width*Channels + x*Channels +1];
				float b = PixelsF[y*Width*Channels + x*Channels +2];
				AverageRate.x += r;
				AverageRate.y += g;
				AverageRate.z += b;
			}
			AverageRate *= 1.0f/samples;
			if (UseHalfFloat)
				glTexImage2D(GL_TEXTURE_2D,0,Channels==3?GL_RGB16F_ARB:GL_RGBA16F_ARB,Width,Height,0,Channels==3?GL_RGB:GL_RGBA,GL_FLOAT,PixelsF);
			else
				glTexImage2D(GL_TEXTURE_2D,0,Channels==3?GL_RGB32F_ARB:GL_RGBA32F_ARB,Width,Height,0,Channels==3?GL_RGB:GL_RGBA,GL_FLOAT,PixelsF);
		}

	}

	vec3 Texture::GetAverageColor()
	{
		return AverageRate;
	}

	void Texture::Bind()
	{
		glBindTexture(GL_TEXTURE_2D,TexID);
	}
}